<!DOCTYPE html>
<html>

<head>
  <title>threejs</title>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=2,maximum-scale=1,user-scalable=yes">
  <meta http-equiv="X-UA-Compatible"  content="IE=edge,chrome=1"   />

  <style>
    html,
    body {
      padding: 0;
      margin: 0;
      overflow: hidden;
    }

    canvas {
      width: 100%;
      height: 100%
    }

    .tip {
      position: fixed;
      top: 5px;
      left: 10px;
      color: blueviolet;
      font-size: 12px;
    }
  </style>
</head>

<body>
  <div class="tip">点击地面，即可自由行走</div>

  <script type="module">
    import * as THREE from 'https://cdn.skypack.dev/three@v0.129.0';
    import { GLTFLoader } from 'https://cdn.skypack.dev/three@v0.129.0/examples//jsm/loaders/GLTFLoader.js';
    import { OrbitControls } from 'https://cdn.skypack.dev/three@v0.129.0/examples/jsm/controls/OrbitControls.js';

    // 创建渲染器
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight) // 设置canvas画布大小
    document.body.appendChild(renderer.domElement); // canvas画布插入dom树

    // 创建场景
    var scene = new THREE.Scene();
    scene.background = new THREE.Color('#aaa');


    // 辅助线
    var axisHelper = new THREE.AxisHelper(500);
    scene.add(axisHelper);

    // 添加点光源
    let light1 = new THREE.PointLight('#fff', 2);
    light1.position.set(0, 1160, -22160);
    scene.add(light1)

    //环境光
    let ambient = new THREE.AmbientLight('#fff', 1);
    scene.add(ambient)

    // 创建相机
    var camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 4000);
    camera.position.set(100, 600, -750) // 设置相机位置


    // 创建控制器
    let controls = new OrbitControls(camera, renderer.domElement)
    controls.autoRotate = true

    // 渲染
    !(function render() {
      controls.update() // Update controls
      renderer.render(scene, camera);
      requestAnimationFrame(render)
    })()


    // 创建平面，几何体，材质
    const texture = new THREE.TextureLoader().load('http://182.43.179.137:81/public/images/texture-wood2.jpg'); // 立方纹理加载器
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.repeat.set(10, 10);
    var geometry = new THREE.PlaneGeometry(3000, 3000);; // 平面几何体
    var material = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide });
    var plane = new THREE.Mesh(geometry, material); // 创建模型
    plane.rotateX(Math.PI / 180 * 90)
    scene.add(plane) // 加入场景


    // 加载模型
    let gltfModel
    let idleClip, runClip, walkClip // 轨道剪辑
    let idleAction, runAction, walkAction // 动画行为
    new GLTFLoader().load('http://182.43.179.137:81/public/3d-models/Soldier.glb', function (gltf) {
      gltfModel = gltf.scene;
      gltfModel.children[0].scale.set(1, 1, 1) // 缩放修正
      scene.add(gltfModel); // 模型加入场景

      const animations = gltf.animations;
      let mixer = new THREE.AnimationMixer(gltfModel); // 混合器
      idleClip = animations[0] // 空闲
      runClip = animations[1] // 跑动
      walkClip = animations[3] // 行走

      idleAction = mixer.clipAction(idleClip);
      idleAction.timeScale = 2 // 2倍速
      runAction = mixer.clipAction(runClip);
      walkAction = mixer.clipAction(walkClip);
      walkAction.timeScale = 3 // 3倍速

      idleAction.play()

      // 更新混合器
      var clock = new THREE.Clock();
      setInterval(() => {
        mixer.update(clock.getDelta());
      }, 50)

    });

    // 自由行走
    !(function () {

      let initPos = null
      let timer_interval = null
      let timer_timeout = null

      // 记录初始位置
      renderer.domElement.addEventListener('pointerdown', function (event) {
        event = event || window.event
        initPos = {
          x: event.clientX,
          y: event.clientY,
        }
      })

      // 行走实现
      renderer.domElement.addEventListener('pointerup', function (event) {
        event = event || window.event
        if (!initPos || Math.abs(initPos.x - event.clientX) > 2 || Math.abs(initPos.y - event.clientY) > 2) return
        initPos = null

        // 触点坐标
        let x = event.clientX - renderer.domElement.getBoundingClientRect().left
        let y = event.clientY - renderer.domElement.getBoundingClientRect().top
        let canvasWidth = renderer.domElement.clientWidth // canvas画布宽度
        let canvasHeight = renderer.domElement.clientHeight // canvas画布高度

        // 坐标转换（归一化的值）
        var sx = -1 + (x / canvasWidth) * 2;
        var sy = 1 - (y / canvasHeight) * 2;

        // 光线投射器
        var raycaster = new THREE.Raycaster()
        raycaster.setFromCamera(new THREE.Vector2(sx, sy), camera)
        var intersects = raycaster.intersectObjects([plane]);

        // 拾取地面坐标
        if (intersects.length > 0) {

          const newPos = intersects[0].point // 新位置
          const originPos = gltfModel.position.clone() // 老位置
          const cameraOriginPos = camera.position.clone() // 相机老位置
          const clock = new THREE.Clock()
          const speed = 1000 // 移动速度
          const vector = newPos.clone().sub(originPos)
          const distance = vector.length()

          // 修正方向
          const quaternion = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, -1), vector.clone().normalize());
          gltfModel.quaternion.copy(quaternion)
          gltfModel.quaternion.copy(quaternion)

          // 动画转换
          walkAction.play()
          idleAction.stop()

          timer_interval && clearInterval(timer_interval) // 清timer
          timer_timeout && clearInterval(timer_timeout)

          // 更新位置
          timer_interval = setInterval(() => {
            const moveLength = clock.getElapsedTime() * speed
            const moveVector = vector.clone().multiplyScalar(moveLength / distance)
            const movePos = originPos.clone().add(moveVector)
            gltfModel.position.copy(movePos)
            camera.position.copy(cameraOriginPos.clone().add(moveVector))
            controls.target.copy(movePos)
          }, 50)

          // 清定时器，并最终定位
          timer_timeout = setTimeout(() => {
            clearInterval(timer_interval)
            gltfModel.position.copy(newPos)
            controls.target.copy(newPos)
            idleAction.play();
            walkAction.stop();

          }, distance / speed * 1000)

        }

      })

    })()


  </script>
</body>

</html>